/**
 * A wizard widget that actually works with minimal configuration. (per jQuery's design philosophy)
 *
 * @name	jWizard jQuery UI Widget
 * @author	Dominic Barnes
 *
 * @requires jQuery
 * @requires jQuery UI (Widget Factory; ProgressBar optional; Button optional)
 * @version  1.6.2
 */
(function($) {
    /**
     * @class The jWizard object will be fed into $.widget()
     */
    $.widget("db.jWizard", {
        /**
         * @private
         * @property int _stepIndex Represents the index of the current active/visible step
         */
        _stepIndex: 0,
        /**
         * @private
         * @property int _stepCount Represents the `functional` number of steps
         */
        _stepCount: 0,
        /**
         * @private
         * @property int _actualCount Represents the `actual` number of steps
         */
        _actualCount: 0,
        /**
         * @description Initializes jWizard
         * @return void
         */
        _create: function() {
            this._buildSteps();
            this._buildTitle();

            if (this.options.menuEnable) {
                this._buildMenu();
            }

            this._buildButtons();

            if (this.options.counter.enable) {
                this._buildCounter();
            }

            this.element.addClass("ui-widget jw-widget");

            this.element.find(".ui-state-default").live("mouseover mouseout", function(event) {
                if (event.type === "mouseover") {
                    $(this).addClass("ui-state-hover");
                } else {
                    $(this).removeClass("ui-state-hover");
                }
            });

            this._changeStep(this._stepIndex, true);
        },
        /**
         * @private
         * @description Additional processing before destroying the widget.
         *    Will eventually be used to restore everything to it's pre-widget state.
         * @return void
         */
        destroy: function() {
            this._destroySteps();
            this._destroyTitle();

            if (this.options.menuEnable) {
                this._destroyMenu();
            }

            this._destroyButtons();

            if (this.options.counter.enable) {
                this._destroyCounter();
            }

            this.element.removeClass("ui-widget");
            this.element.find(".ui-state-default").unbind("mouseover").unbind("mouseout");

            $.Widget.prototype.destroy.call(this);
        },
        /**
         * @public
         * @description Disables the wizard (mainly the buttons)
         */
        disable: function() {
            this.element.addClass("ui-state-disabled").find("button").attr("disabled", "disabled");
        },
        /**
         * @public
         * @description Disables the wizard (mainly the buttons)
         */
        enable: function() {
            this.element.removeClass("ui-state-disabled").find("button").removeAttr("disabled");
        },
        /**
         * @private
         * @description Can set options within the widget programmatically
         * @return void
         */
        _setOption: function(key, value) {
            var keys = key.split('.');

            if (keys.length > 1) {
                switch (keys[0]) {
                    case "buttons":
                        this.options[keys[0]][keys[1]] = value;

                        switch (keys[1]) {
                            case "jqueryui":
                                this.options[keys[0]][keys[1]][keys[2]] = value;
                                if (keys[2] === "enable") {
                                    if (value) {
                                        this.find(".jw-buttons > button").button("destroy");
                                    } else {
                                        this._destroyButtons();
                                        this._buildButtons();
                                    }
                                    break;
                                }
                                break;
                            case "cancelHide":
                                this.element.find(".jw-button-cancel")[value ? "addClass" : "removeClass"]("ui-helper-hidden");
                                break;
                            case "cancelType":
                                this.element.find(".jw-button-cancel").attr("type", value);
                                break;
                            case "finishType":
                                this.element.find(".jw-button-finish").attr("type", value);
                                break;
                            case "cancelText":
                                this.element.find(".jw-button-cancel").text(value);
                                break;
                            case "previousText":
                                this.element.find(".jw-button-previous").text(value);
                                break;
                            case "nextText":
                                this.element.find(".jw-button-next").text(value);
                                break;
                            case "finishText":
                                this.element.find(".jw-button-finish").text(value);
                                break;
                        }
                        break;

                    case "counter":
                        this.options[keys[0]][keys[1]] = value;

                        switch (keys[1]) {
                            case "enable":
                                if (value) {
                                    this._buildCounter();
                                    this._updateCounter();
                                } else {
                                    this._destroyCounter();
                                }
                                break;
                            case "type":
                            case "progressbar":
                            case "location":
                                if (this.options.counter.enable) {
                                    this._destroyCounter();
                                    this._buildCounter();
                                    this._updateCounter();
                                }
                                break;
                            case "startCount":
                            case "startHide":
                            case "finishCount":
                            case "finishHide":
                            case "appendText":
                            case "orientText":
                                if (this.options.counter.enable) {
                                    this._updateCounter();
                                }
                                break;
                        }
                        break;

                    case "effects":
                        if (keys.length === 2) {
                            this.options[keys[0]][keys[1]] = value;
                        } else {
                            this.options[keys[0]][keys[1]][keys[2]] = value;
                        }
                        break;
                }
            } else {
                this.options[keys[0]] = value;

                switch (keys[0]) {
                    case "titleHide":
                        this.element.find(".jw-header")[value ? "addClass" : "removeClass"]("ui-helper-hidden");
                        break;

                    case "menuEnable":
                        if (value) {
                            this._buildMenu();
                            this._updateMenu();
                        } else {
                            this._destroyMenu();
                        }
                        break;

                    case "counter":
                        this._destroyCounter();
                        this._buildCounter();
                        this._updateCounter();
                        break;
                }
            }
        },
        /**
         * @description Jumps to the first step in the wizard's step collection
         * @return void
         */
        firstStep: function() {
            this.changeStep(0, "first");
        },
        /**
         * @description Jumps to the last step in the wizard's step collection
         * @return void
         */
        lastStep: function() {
            this.changeStep(this._stepCount - 1, "last");
        },
        /**
         * @description Jumps to the next step in the wizard's step collection
         * @return void
         */
        nextStep: function() {
            var options = {
                wizard: this.element,
                currentStepIndex: this._stepIndex,
                nextStepIndex: this._stepIndex + 1,
                delta: 1
            };

            if (this._trigger("next", null, options) !== false) {
                this.changeStep(this._stepIndex + 1, "next");
            }

            var self = this;
            if (self.options.events.onNext !== undefined) {
                var event = new Object();
                event.stepCount = self._stepCount;
                event.stepIndex_Prev = options.currentStepIndex;
                event.stepIndex = options.nextStepIndex;
                self.options.events.onNext(event);
            }
        },
        /**
         * @description Jumps to the previous step in the wizard's step collection
         * @return void
         */
        previousStep: function() {
            var options = {
                wizard: this.element,
                currentStepIndex: this._stepIndex,
                nextStepIndex: this._stepIndex - 1,
                delta: -1
            };

            if (this._trigger("previous", null, options) !== false) {
                this.changeStep(this._stepIndex - 1, "previous");
            }

            var self = this;
            if (self.options.events.onPrevious !== undefined) {
                var event = new Object();
                event.stepCount = self._stepCount;
                event.stepIndex_Prev = options.currentStepIndex;
                event.stepIndex = options.nextStepIndex;
                self.options.events.onPrevious(event);
            }
        },
        /**
         * @description Goes to an arbitrary `step` in the collection based on input
         * @return void
         */
        changeStep: function(nextStep, type) {
            type = type || "manual";
            nextStep = typeof nextStep === "number" ? nextStep : $(nextStep).index();
            var options = {
                wizard: this.element,
                currentStepIndex: this._stepIndex,
                nextStepIndex: nextStep,
                delta: nextStep - this._stepIndex,
                type: type
            };

            if (this._trigger("changestep", null, options) !== false) {
                this._changeStep(nextStep);
            }
        },
        /**
         * @private
         * @description Internal wrapper for performing animations
         * @return void
         */
        _effect: function($element, action, subset, type, callback) {
            var wizard = this,
                    opt = this.options.effects[action][subset];

            type = type || "effect";

            if (!$element.length || !$element.hasClass("jw-animated")) {
                $element[type]();

                if (callback) {
                    callback.call(this);
                }

                return false;
            }

            opt.callback = callback || $.noop;

            $element[type](opt.type, opt.options, opt.duration, opt.callback);
        },
        /**
         * @private
         * @description Internal wrapper for logging (and potentially debugging)
         * @return void
         */
        _log: function() {
            if (this.options.debug && window.console) {
                console.log[console.firebug ? "apply" : "call"](console, Array.prototype.slice.call(arguments));
            }
        },
        _updateNavigation: function(firstStep) {
            this._updateButtons();
            if (this.options.menuEnable) {
                this._updateMenu(firstStep);
            }
            if (this.options.counter.enable) {
                this._updateCounter(firstStep);
            }
        },
        /**
         * @private
         * @description Generates the header/title
         * @return void
         */
        _buildTitle: function() {
            this.element.prepend($("<div />", {
                "class": "jw-header ui-widget-header ui-corner-top" + (this.options.hideTitle ? " ui-helper-hidden" : ""),
                html: '<h2 class="jw-title' + ((this.options.effects.enable || this.options.effects.title.enable) ? " jw-animated" : "") + '" />'
            }));
        },
        /**
         * @private
         * @description Updates the title
         * @return void
         */
        _updateTitle: function(firstStep) {
            var wizard = this,
                    $title = this.element.find(".jw-title"),
                    $currentStep = this.element.find(".jw-step:eq(" + this._stepIndex + ")");

            if (!firstStep) {
                this._effect($title, "title", "hide", "hide", function() {
                    $title.text($currentStep.attr("title"));
                    wizard._effect($title, "title", "show", "show");
                });
            } else {
                $title.text($currentStep.attr("title"));
            }
        },
        /**
         * @private
         * @description Destroys the title element (used in `destroy()`)
         * @return void
         */
        _destroyTitle: function() {
            $(".jw-header").remove();
        },
        /**
         * @private
         * @description Initializes the step collection.
         *    Any direct children <div> (with a title attr) or <fieldset> (with a <legend>) are considered steps, and there should be no other sibling elements.
         *    All steps without a specified `id` attribute are assigned one based on their index in the collection.
         *    Lastly, a <div> is wrapped around all the steps to isolate them from the rest of the widget.
         * @return void
         */
        _buildSteps: function() {
            var $steps = this.element.children("div, fieldset");

            this._stepCount = $steps.length;

            $steps.addClass("jw-step").each(function(x) {
                var $step = $(this);

                if (this.tagName.toLowerCase() === "fieldset") {
                    $step.attr("title", $step.find("legend").text());
                }
            });

            if (this.options.effects.enable || this.options.effects.step.enable) {
                $steps.addClass("jw-animated");
            }

            $steps.hide().wrapAll($("<div />", {
                "class": "jw-content ui-widget-content ui-helper-clearfix",
                html: '<div class="jw-steps-wrap" />'
            })).eq(this._stepIndex).show();
        },
        /**
         * @private
         * @description Destroys the step wrappers and restores the steps to their original state (used in `destroy()`)
         * @return void
         */
        _destroySteps: function() {
            $(".jw-step").show().unwrap().unwrap();	// Unwrap 2x: .jw-steps-wrap + .jw-content
            $(".jw-step").unbind("show").unbind("hide").removeClass("jw-step");
        },
        /**
         * @private
         * @description Changes the "active" step.
         * @param number|jQuery nextStep Either an index or a jQuery object/element
         * @param bool isInit Behavior needs to change if this is called during _init (as opposed to manually through the global setter)
         * @return void
         */
        _changeStep: function(nextStep, firstStep) {
            var wizard = this,
                    $steps = this.element.find(".jw-step"),
                    $currentStep = $steps.eq(this._stepIndex);

            if (typeof nextStep === "number") {
                if (nextStep < 0 || nextStep > ($steps.length - 1)) {
                    alert("Index " + nextStep + " Out of Range");
                    return false;
                }

                nextStep = $steps.eq(nextStep);
            } else if (typeof nextStep === "object") {
                if (!nextStep.is($steps.selector)) {
                    alert("Supplied Element is NOT one of the Wizard Steps");
                    return false;
                }
            }

            if (!firstStep) {

                // Evento
                var self = this;
                var prevIndex = self._stepIndex;

                this._disableButtons();
                this._stepIndex = $steps.index(nextStep);
                this._updateTitle(firstStep);

                // Evento
                if (self.options.events.onChange !== undefined) {
                    var event = new Object();
                    event.stepCount = self._stepCount;
                    event.stepIndex = self._stepIndex;
                    event.stepIndex_Prev = prevIndex;
                    self.options.events.onChange(event);
                }

                this._effect($currentStep, "step", "hide", "hide", function() {
                    wizard._effect(nextStep, "step", "show", "show", function() {
                        wizard._enableButtons();
                        wizard._updateNavigation(firstStep);
                    });
                });
            } else {
                this._stepIndex = $steps.index(nextStep);
                this._updateTitle(firstStep);
                this._updateNavigation(firstStep);
            }
        },
        /**
         * @private
         * @description Initializes the menu
         *    Builds the menu based on the collection of steps
         *    Assigns a class to the main <div> to indicate to CSS that there is a menu
         *    Binds a click event to each of the <a> that will change the step accordingly when clicked
         * @return void
         */
        _buildMenu: function() {
            var list = [], $menu, $anchors;

            this.element.addClass("jw-hasmenu");
            this.element.find(".jw-step").each(function(x) {
                list.push($("<li />", {
                    "class": "ui-corner-all " + (x === 0 ? "jw-current ui-state-highlight" : "jw-inactive ui-state-disabled"),
                    html: $("<a />", {
                        step: x,
                        text: $(this).attr("title")
                    })
                })[0]);
            });

            $menu = $("<div />", {
                "class": "jw-menu-wrap",
                html: $("<div />", {
                    "class": "jw-menu",
                    html: $("<ol />", { html: $(list)})
                })
            });

            this.element.find(".jw-content").prepend($menu);

            if (this.options.effects.enable || this.options.effects.menu.enable) {
                $menu.find("li").addClass("jw-animated");
            }

            $menu.find("a").click($.proxy(function(event) {
                var $target = $(event.target),
                        nextStep = parseInt($target.attr("step"), 10);

                if ($target.parent().hasClass("jw-active")) {
                    this.changeStep(nextStep, nextStep <= this._stepIndex ? "previous" : "next");
                }
            }, this));
        },
        /**
         * @private
         * @description Removes the 'jw-hasmenu' class and pulls the menu out of the DOM entirely
         * @return void
         */
        _destroyMenu: function() {
            this.element.removeClass("jw-hasmenu").find(".jw-menu-wrap").remove();
        },
        /**
         * @private
         * @description Updates the menu at the end of each call to _changeStep()
         *    Each <a> is looped over, along with the parent <li>
         *    Status (jw-current, jw-active, jw-inactive) set depending on progress through wizard
         * @see this._changeStep()
         * @return void
         */
        _updateMenu: function(firstStep) {
            var wizard = this,
                    currentStep = this._stepIndex,
                    $menu = this.element.find(".jw-menu");

            if (!firstStep) {
                this._effect($menu.find("li:eq(" + currentStep + ")"), "menu", "change");
            }

            $menu.find("a").each(function(x) {
                var $a = $(this),
                        $li = $a.parent(),
                        iStep = parseInt($a.attr("step"), 10),
                        sClass = "";

                if (iStep < currentStep) {
                    sClass += "jw-active ui-state-default";
                } else if (iStep === currentStep) {
                    sClass += "jw-current ui-state-highlight";
                } else if (iStep > currentStep) {
                    sClass += "jw-inactive ui-state-disabled";
                    $a.removeAttr("href");
                }

                $li.removeClass("jw-active jw-current jw-inactive ui-state-default ui-state-highlight ui-state-disabled").addClass(sClass);
            });
        },
        /**
         * @private
         * @description Initializes the step counter.
         *    A new <span> is created and used as the main element
         * @return void
         */
        _buildCounter: function() {
            var $counter = $("<span />", { "class": "jw-counter ui-widget-content ui-corner-all jw-" + this.options.counter.orientText});

            if (this.options.counter.location === "header") {
                this.element.find(".jw-header").prepend($counter);
            } else if (this.options.counter.location === "footer") {
                this.element.find(".jw-footer").prepend($counter);
            }

            if (!this.options.counter.startCount) {
                this._stepCount--;
            }
            if (!this.options.counter.finishCount) {
                this._stepCount--;
            }

            if (this.options.effects.enable || this.options.effects.counter.enable) {
                $counter.addClass("jw-animated");
            }

            if (this.options.counter.progressbar) {
                $counter
                        .html('<span class="jw-counter-text" /> <span class="jw-counter-progressbar" />')
                        .find(".jw-counter-progressbar").progressbar();
            }
        },
        /**
         * @private
         * @description This is run at the end of every call to this._changeStep()
         * @return void
         * @see this._changeStep()
         */
        _updateCounter: function(firstStep) {
            var $counter = this.element.find(".jw-counter"),
                    counterOptions = this.options.counter,
                    counterText = "",
                    actualIndex = this._stepIndex,
                    actualCount = this._stepCount,
                    percentage = 0;

            if (!counterOptions.startCount) {
                actualIndex--;
                actualCount--;
            }

            if (!firstStep) {
                this._effect($counter, "counter", "change");
            }

            percentage = Math.round((actualIndex / actualCount) * 100);

            if (counterOptions.type === "percentage") {
                counterText = ((percentage <= 100) ? percentage : 100) + "%";
            } else if (counterOptions.type === "count") {
                if (actualIndex < 0) {
                    counterText = 0;
                } else if (actualIndex > actualCount) {
                    counterText = actualCount;
                } else {
                    counterText = actualIndex;
                }

                counterText += " " + counterOptions.ofText + " " + actualCount;
            } else {
                counterText = "N/A";
            }

            if (counterOptions.appendText) {
                counterText += " " + counterOptions.appendText;
            }

            if (counterOptions.progressbar) {
                this.element.find(".jw-counter-progressbar").progressbar("option", "value", percentage);
                this.element.find(".jw-counter-text").text(counterText);
            } else {
                $counter.text(counterText);
            }

            if ((counterOptions.startHide && this._stepIndex === 0) || (counterOptions.finishHide && this._stepIndex === (this._actualCount - 1))) {
                $counter.hide();
            } else {
                $counter.show();
            }
        },
        /**
         * @private
         * @description Removes the counter DOM elements, resets _stepCount
         * @return void
         */
        _destroyCounter: function() {
            this.element.find(".jw-counter").remove();
        },
        /**
         * @private
         * @description This generates the <button> elements for the main navigation and binds `click` handlers to each of them
         * @see this._changeStep()
         */
        _buildButtons: function() {
            var self = this,
                    options = this.options.buttons,
                    $footer = $("<div />", { "class": "jw-footer ui-widget-header ui-corner-bottom"}),
                    $cancel = $('<button type="' + options.cancelType + '" class="ui-state-default ui-corner-all jw-button-cancel jw-priority-secondary' + (options.cancelHide ? " ui-helper-hidden" : "") + '">' + options.cancelText + '</button>'),
                    $previous = $('<button type="button" class="ui-state-default ui-corner-all jw-button-previous">' + options.previousText + '</button>'),
                    $next = $('<button type="button" class="ui-state-default ui-corner-all jw-button-next">' + options.nextText + '</button>'),
                    $finish = $('<button type="' + options.finishType + '" class="ui-state-default ui-corner-all jw-button-finish ui-state-highlight">' + options.finishText + '</button>');

            $cancel.click(function(event) {
                self._trigger("cancel", event);
            });
            $previous.click(function(event) {
                self.previousStep();
            });
            $next.click(function(event) {
                self.nextStep();
            });
            $finish.click(function(event) {
                if (self.options.events.onFinish !== undefined) {
                    event.stepCount = self._stepCount;
                    event.stepIndex = self._stepIndex;
                    self.options.events.onFinish(event);
                }
                self._trigger("finish", event);
            });

            if (options.jqueryui.enable) {
                $cancel.button({ icons: { primary: options.jqueryui.cancelIcon}});
                $previous.button({ icons: { primary: options.jqueryui.previousIcon}});
                $next.button({ icons: { secondary: options.jqueryui.nextIcon}});
                $finish.button({ icons: { secondary: options.jqueryui.finishIcon}});
            }

            $(".jw-header").append($('<div class="jw-buttons" />').append($cancel).append($previous).append($next).append($finish));
            //this.element.append($footer.append($('<div class="jw-buttons" />').append($cancel).append($previous).append($next).append($finish)));
        },
        /**
         * @private
         * @description Updates the visibility status of each of the buttons depending on the end-user's progress
         * @see this._changeStep()
         */
        _updateButtons: function() {
            var $steps = this.element.find(".jw-step"),
                    $previous = this.element.find(".jw-button-previous"),
                    $next = this.element.find(".jw-button-next"),
                    $finish = this.element.find(".jw-button-finish");

            switch ($steps.index($steps.filter(":visible"))) {
                case 0:
                    $previous.hide();
                    $next.show();
                    $finish.hide();
                    break;

                case $steps.length - 1:
                    $previous.show();
                    $next.hide();
                    $finish.show();
                    break;

                default:
                    $previous.show();
                    $next.show();
                    $finish.hide();
                    break;
            }
        },
        _disableButtons: function() {
            this.element.find(".jw-buttons button").addClass("ui-state-disabled").attr("disabled", true);
        },
        _enableButtons: function() {
            this.element.find(".jw-buttons button").removeClass("ui-state-disabled").attr("disabled", false);
        },
        /**
         * @private
         * @description Updates the visibility status of each of the buttons depending on the end-user's progress
         * @see this._changeStep()
         */
        _destroyButtons: function() {
            this.element.find(".jw-footer").remove();
        },
        /**
         * @property object options This is the set of configuration options available to the user.
         */
        options: {
            debug: false,
            disabled: false,
            titleHide: false,
            menuEnable: false,
            //hotkeys: false,

            buttons: {
                jqueryui: {
                    enable: false,
                    cancelIcon: "ui-icon-circle-close",
                    previousIcon: "ui-icon-circle-triangle-w",
                    nextIcon: "ui-icon-circle-triangle-e",
                    finishIcon: "ui-icon-circle-check"
                },
                cancelHide: false,
                cancelType: "button",
                finishType: "button",
                cancelText: "Cancel",
                previousText: "Previous",
                nextText: "Next",
                finishText: "Finish"
            },
            counter: {
                enable: false,
                type: "count",
                progressbar: false,
                location: "footer",
                startCount: true,
                startHide: false,
                finishCount: true,
                finishHide: false,
                appendText: "Complete",
                ofText: "of",
                orientText: "left"
            },
            effects: {
                enable: false,
                step: {
                    enable: false,
                    hide: {
                        type: "slide",
                        options: { direction: "left"},
                        duration: "fast"
                    },
                    show: {
                        type: "slide",
                        options: { direction: "left"},
                        duration: "fast"
                    }
                },
                title: {
                    enable: false,
                    hide: {
                        type: "slide",
                        duration: "fast"
                    },
                    show: {
                        type: "slide",
                        duration: "fast"
                    }
                },
                menu: {
                    enable: false,
                    change: {
                        type: "highlight",
                        duration: "fast"
                    }
                },
                counter: {
                    enable: false,
                    change: {
                        type: "highlight",
                        duration: "fast"
                    }
                },
                events: {
                    onFinish: undefined,
                    onNext: undefined,
                    onPrevious: undefined,
                    onChange: undefined
                }
            }
        },
        cancel: $.noop,
        previous: $.noop,
        next: $.noop,
        finish: $.noop,
        changestep: function(event, ui) {
            if (event.isDefaultPrevented()) {
                if (typeof event.nextStepIndex !== "undefined") {
                    ui.wizard.jWizard("changeStep", event.nextStepIndex);
                    return false;
                }
            }
        }
    });
}(jQuery));
